package com.pactera.asmp.server.common.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
//import com.pactera.asmp.server.common.constants.OccupyStatus;
import com.pactera.asmp.server.common.constants.ResultCode;
import com.pactera.asmp.server.common.exception.CustomException;
import com.pactera.asmp.server.common.websocket.config.ServerConfig;
//import com.pactera.asmp.server.dao.TaskDeviceMapper;
//import com.pactera.asmp.server.entity.TaskDevice;
//import com.pactera.asmp.server.job.dispatch.impl.compete.CompeteDataRepository;
import com.pactera.asmp.server.pojo.SocketMessage;
import com.pactera.asmp.server.pojo.WSMessage;
import com.pactera.asmp.server.service.IDeviceServcie;
import com.pactera.asmp.server.service.TaskService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.websocket.Session;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class SocketUtil {
    public static final String REDIS_KEY_DEVICE_PREFIX = "WS_DEVICE_";
    public static final String REDIS_KEY_SCREEN_PREFIX = "WS_SCREEN_";
    public static final String REDIS_KEY_SOCKET_SERVER_USER = "WS_USER_SERVER";                     // 用户与服务器地址的绑定 （HASH）  eg: WS_USER_SERVER  -->  [{ 13@abc , 172_17_5_22_8080 }]
    public static final String REDIS_KEY_WS_USER_INSTANCE = "WS_USER_INSTANCE";                     // 用户多个socket实例  （SET）      eg: WS_USER_INSTANCE:46  --> [ 46@abcd ]
    public static final String REDIS_KEY_SOCKET_SERVER_DEVICE = "WS_DEVICE_SERVER";                 // 设备与服务器地址的绑定 （HASH）  eg: WS_DEVICE_SERVER  -->  [{ xxxx-xxxx-xxxx-xxxx@abcd , 172_17_5_22_8080}]
    public static final String REDIS_KEY_WS_DEVICE_INSTANCE = "WS_DEVICE_INSTANCE";                 // 设备多个socket实例 （SET）       eg: WS_DEVICE_INSTANCE:xxxx-xxxx-xxxx-xxxx  -->  [ xxxx-xxxx-xxxx-xxxx@abcd ]
    public static final String REDIS_KEY_SOCKET_SERVER_SCREEN = "WS_SCREEN_SERVER";                 // 大屏与服务器地址的绑定（HASH）   eg: WS_SCREEN_SERVER -->  [{ sceen:13@abc , 172_17_5_22_8080 }]
    public static final String REDIS_KEY_WS_SCREEN_INSTANCE = "WS_SCREEN_INSTANCE";                 // 大屏多个socket实例（SET）        eg: WS_SCREEN_INSTANCE:sceen:46 -->  [ sceen:46@abcd ]
    public static final String REDIS_KEY_SET_SCREEN_SESSION_DEVICE = "WS_SCREEN_SESSION_DEVICE";    // 大屏绑定的多个设备IMAGE（SET）   eg: WS_SCREEN_SESSION_DEVICE:sceen:13@abcd -->  [ xxxx-xxxx-xxx1 , xxxx-xxxx-xxx2 ]
    public static final String REDIS_KEY_SET_SCREEN_DEVICE_SESSION = "WS_SCREEN_DEVICE_SESSION";    // 设备关联的大屏用户（SET）        eg: WS_SCREEN_DEVICE_SESSION:xxxx-xxxx-xxx1 -->  [ sceen:13@abcd, sceen:16@abcd ]
    public static final String REDIS_KEY_HASH_SCREEN_SESSION_PROJECT = "WS_SCREEN_SESSION_PROJECT"; // 大屏实例绑定项目（HASH）         eg: WS_SCREEN_SESSION_PROJECT sceen:13@abcd -->  13
    public static final String REDIS_KEY_SET_SCREEN_PROJECT_SESSION = "WS_SCREEN_PROJCET_SESSION";  // 项目绑定大屏实例（SET）          eg: WS_SCREEN_PROJCET_SESSION:13 --> [sceen:13@abcd,sceen:12@abcd]
    public static final String REDIS_KEY_SET_SCREEN_SESSION_COMPUTER = "WS_SCREEN_SESSION_COMPUTER";// 大屏绑定上位机（SET）            eg: WS_SCREEN_SESSION_COMPUTER:sceen:13@abcd --> [cuid1,cuid2]
    public static final String REDIS_KEY_SET_SCREEN_COMPUTER_SESSION = "WS_SCREEN_COMPUTER_SESSION";// 上位机绑定大屏实例（SET）        eg: WS_SCREEN_COMPUTER_SESSION:cuid1 -->  [ sceen:13@abcd, sceen:16@abcd ]
    public static final String REDIS_KEY_SET_SCREEN_SESSION_DUID = "WS_SCREEN_SESSION_DUID";        // 大屏绑定车机设备（SET）          eg: WS_SCREEN_SESSION_DUID:sceen:13@abcd -->  [ duid1,duid2 ]
    public static final String REDIS_KEY_SET_SCREEN_DUID_SESSION = "WS_SCREEN_DUID_SESSION";        // 车机设备绑定大屏实例（SET）      eg: WS_SCREEN_DUID_SESSION:duid1 --> [ sceen:13@abcd, sceen:16@abcd ]
    public static final String REDIS_KEY_WS_DEVICE_PACKAGE_NAME = "WS_DEVICE_PACKAGE_NAME";         // 设备的包名

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    @Lazy
    private HeartBeatService heartBeatService;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ServerConfig serverConfig;
    @Autowired
    private TaskService taskService;
    @Autowired
    private IDeviceServcie deviceServcie;
    @Autowired
    private DirectReceiver directReceiver;
//    @Autowired
//    private TaskDeviceMapper taskDeviceMapper;
//
//    @Autowired
//    private CompeteDataRepository repository;

    @Autowired
    @Qualifier("bindScreenScript")
    private DefaultRedisScript bindScreenScript;

    @Autowired
    @Qualifier("unBindScreenScript")
    private DefaultRedisScript unBindScreenScript;


    @Autowired
    @Qualifier("bindScreenProjectScript")
    private DefaultRedisScript bindScreenProjectScript;

    @Autowired
    @Qualifier("bindScreenComputerScript")
    private DefaultRedisScript bindScreenComputerScript;

    /**
     * 注册WebSocket与本地服务的绑定  id@1234  -->  172_17_5_22_8080
     * @param id
     */
    public void registerSocketServer(String id){
        if(StringUtils.isEmpty(id) || id.indexOf('@') == -1) throw new CustomException(ResultCode.PARAM_NOT_COMPLETE);
        String tmpId = id.split("@")[0];

        if(tmpId.startsWith("screen:")) {
            redisTemplate.opsForHash().put(REDIS_KEY_SOCKET_SERVER_SCREEN, id, serverConfig.getUrl());
            redisTemplate.opsForSet().add(REDIS_KEY_WS_SCREEN_INSTANCE+":"+tmpId,id);
        }
        else if(tmpId.length() == 36){
            redisTemplate.opsForHash().put(REDIS_KEY_SOCKET_SERVER_DEVICE, id, serverConfig.getUrl());
            redisTemplate.opsForSet().add(REDIS_KEY_WS_DEVICE_INSTANCE+":"+tmpId,id);// 如果是设备，同时绑定设备ID和实例的映射
        }
        else {
            redisTemplate.opsForHash().put(REDIS_KEY_SOCKET_SERVER_USER, id, serverConfig.getUrl());
            redisTemplate.opsForSet().add(REDIS_KEY_WS_USER_INSTANCE+":"+tmpId,id);
        }
    }

    /**
     * 自定义sessionId   (id@1234)
     * @param id
     * @param session
     * @return
     */
    public String formatSessionId(String id, Session session){
        if(StringUtils.isEmpty(id) || session == null || session.toString().indexOf('@') == -1) return null;
        try {
            return id+"@"+session.toString().split("@")[1];
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过onOpen.sessionId心跳续约
     * @param id
     */
    public void heartBeatById(String id) {
        try {
            if (StringUtils.isEmpty(id)) return;
            heartBeatService.online(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过心跳消息ping#id@1234进行socket续约
     * @param message
     */
    public void heartBeatByMsg(String message){
        try {
            if (StringUtils.isEmpty(message)) return;
            if (message.indexOf('#') == -1) return;
            String id = message.split("#")[1];
            heartBeatService.online(id);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 清除心跳缓存
     * @param id
     */
    public void offlineRedisCache(String id) {
        try {
            heartBeatService.remove(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送MQ消息
     * @param from   aa@abcd  (带@)
     * @param to   abc  (不带@)
     * @param message
     * @param type
     * @param params
     * @return
     */
    public boolean sendMQ(String from,String to, byte[] message, String type, Map<String,Object> params) {
        try {
            if (StringUtils.isEmpty(to)  || to.indexOf('@') != -1) return false;
            String _to = null;
            String _displayId = null;
            if (to.indexOf('#') != -1) {
                String[] strs = to.split("#");
                _to = strs[0];
                _displayId = strs[1];
            }else {
                _to = to;
            }
            String routingKey;
            String _from = from.indexOf('@') == -1? from:from.split("@")[0];
            Set<String> members;
            SocketMessage msg = new SocketMessage(_from, _to, _displayId, type, message, params);
            final String wsDirectExchange = "WsDirectExchange";
            if(_to.startsWith("screen:")) {  // 通知大屏用户
                members = redisTemplate.opsForSet().members(REDIS_KEY_WS_SCREEN_INSTANCE + ":" + _to);
                for(String member : members){
                    routingKey = (String) redisTemplate.opsForHash().get(REDIS_KEY_SOCKET_SERVER_SCREEN, member);
//                    rabbitTemplate.convertAndSend(wsDirectExchange, routingKey, msg);
                    directReceiver.process(msg);
                }
            }
            else if(_to.length() == 36){  // 通知设备
               members = redisTemplate.opsForSet().members(REDIS_KEY_WS_DEVICE_INSTANCE + ":" + _to);
                for(String member : members){
                    routingKey = (String) redisTemplate.opsForHash().get(REDIS_KEY_SOCKET_SERVER_DEVICE, member);
//                    rabbitTemplate.convertAndSend(wsDirectExchange, routingKey, msg);
                    directReceiver.process(msg);
                }
            }
            else {  // 通知用户
                members = redisTemplate.opsForSet().members(REDIS_KEY_WS_USER_INSTANCE + ":" + _to);
                for(String member : members){
                    routingKey = (String) redisTemplate.opsForHash().get(REDIS_KEY_SOCKET_SERVER_USER, member);
//                    rabbitTemplate.convertAndSend(wsDirectExchange, routingKey, msg);
                    directReceiver.process(msg);
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendMQ(String routingKey,SocketMessage message) {
        try {
//            rabbitTemplate.convertAndSend("WsDirectExchange", routingKey, message);
            directReceiver.process(message);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * websocket连接成功时返回信息
     * @param session
     * @param id
     * @param socketKey
     */
    public void openSuccessMessage(Session session, String id,String socketKey) {
        this.openSuccessMessage(session, id, socketKey,null);
    }
    public void openSuccessMessage(Session session, String id,String socketKey,String params) {
        WSMessage wsMessage = new WSMessage();
        wsMessage.setMsgcode("200");
        wsMessage.setTouuid(id);
        wsMessage.setFromuuid("server");
        wsMessage.setSocketKey(socketKey);
        wsMessage.setDescribe("连接成功!");
        if(!StringUtils.isEmpty(params)){
            wsMessage.setList(Arrays.asList(params.split(",")));
        }
        try {
            session.getBasicRemote().sendText(objectMapper.writeValueAsString(wsMessage));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * socket.onClose时更新任务和设备状态
     * @param id
     */
    @Transactional
    public void updateDeviceStatus(String id) {
        if (StringUtils.isEmpty(id)) return;
        taskService.interrupterWhenExecing(id); // 任务执行状态修改为失败
        deviceServcie.updateStatus(id, 2);  // 上位机状态修改
//        // 释放占用设备
//        List<TaskDevice> deviceList = repository.getTaskDeviceListByDeviceAndStatus(id, OccupyStatus.OCCUPIED.getValue());
//        for(TaskDevice taskDevice : deviceList){
//            taskDevice.setOccupyStatus(OccupyStatus.RELEASED.getValue());
//            taskDeviceMapper.updateByPrimaryKey(taskDevice);
//        }
    }

    /**
     * 大屏绑定多设备
     * @param sessionKey
     * @param deviceIds
     */
    public String bindScreenSocketDevices(String sessionKey, String deviceIds) {
        // 1. 原有绑定设备需要先去除
        // 2. 绑定现有指定设备
        // 3. 绑定时判断指定设备是否在线，是否可以正常绑定
        if(StringUtils.isEmpty(sessionKey) || StringUtils.isEmpty(deviceIds)) throw new CustomException(ResultCode.PARAM_NOT_COMPLETE);

        List<String> keys = new ArrayList<>();
        keys.add(REDIS_KEY_SET_SCREEN_SESSION_DEVICE);
        keys.add(REDIS_KEY_SET_SCREEN_DEVICE_SESSION);
        keys.add(HeartBeatService.REDIS_KEY_WS_ONLINE_MEMBERS);
        keys.add(sessionKey);
        keys.add(REDIS_KEY_WS_DEVICE_INSTANCE);
        List<String> deviceIdStrs = Lists.newArrayList(deviceIds.split(","));
        deviceIdStrs = deviceIdStrs.stream().map(x -> x.split("#")[0]).distinct().collect(Collectors.toList());
        Object execute = redisTemplate.execute(bindScreenScript, keys,deviceIdStrs.toArray(new String[deviceIdStrs.size()]));
        return execute.toString();
    }

    /**
     * 大屏绑定上位机
     * @param sessionKey
     * @param cuids
     */
    public String bindScreenSockeComputer(String sessionKey, String cuids) {
        // 1. 原有绑定上位机需要先去除
        // 2. 绑定现有指定上位机
        // 3. 绑定时判断指定上位机是否在线，是否可以正常绑定
        if(StringUtils.isEmpty(sessionKey) || StringUtils.isEmpty(cuids)) throw new CustomException(ResultCode.PARAM_NOT_COMPLETE);
        List<String> keys = new ArrayList<>();
        keys.add(REDIS_KEY_SET_SCREEN_SESSION_COMPUTER);
        keys.add(REDIS_KEY_SET_SCREEN_COMPUTER_SESSION);
        keys.add(HeartBeatService.REDIS_KEY_WS_ONLINE_MEMBERS);
        keys.add(sessionKey);
        keys.add(REDIS_KEY_WS_DEVICE_INSTANCE);
        Object execute = redisTemplate.execute(bindScreenComputerScript, keys,cuids.split(","));
        return execute.toString();
    }

    /**
     * 大屏绑定设备（运行状态）
     * @param sessionKey
     * @param duids
     */
    public String bindScreenSockeDuidState(String sessionKey, String duids) {
        // 1. 原有绑定设备需要先去除
        // 2. 绑定现有指定设备
        // 3. 绑定时判断指定设备是否在线，是否可以正常绑定
        if(StringUtils.isEmpty(sessionKey) || StringUtils.isEmpty(duids)) throw new CustomException(ResultCode.PARAM_NOT_COMPLETE);
        List<String> keys = new ArrayList<>();
        keys.add(REDIS_KEY_SET_SCREEN_SESSION_DUID);
        keys.add(REDIS_KEY_SET_SCREEN_DUID_SESSION);
        keys.add(HeartBeatService.REDIS_KEY_WS_ONLINE_MEMBERS);
        keys.add(sessionKey);
        keys.add(REDIS_KEY_WS_DEVICE_INSTANCE);
        // bindScreenComputerScript可共用
        Object execute = redisTemplate.execute(bindScreenComputerScript, keys,duids.split(","));
        return execute.toString();
    }

    /**
     * 解除大屏绑定关系
     * @param sessionKey
     */
    public String clearScreenRegister(String sessionKey) {
        // 1. 解除大屏用户与设备的绑定
        // 2. 解除socket与server的绑定关联
        // 3. 解除socket与项目的绑定关联
        // 4. 解除socket与上位机的绑定关联
        // 5. 解除socket与状态设备的绑定关联
        // 6. 解除socklet与实例的绑定关联
        if(StringUtils.isEmpty(sessionKey)) throw new CustomException(ResultCode.PARAM_NOT_COMPLETE);

        List<String> keys = new ArrayList<>();
        keys.add(REDIS_KEY_SET_SCREEN_SESSION_DEVICE);
        keys.add(REDIS_KEY_SET_SCREEN_DEVICE_SESSION);
        keys.add(REDIS_KEY_SOCKET_SERVER_SCREEN);
        keys.add(sessionKey);
        keys.add(REDIS_KEY_HASH_SCREEN_SESSION_PROJECT);
        keys.add(REDIS_KEY_SET_SCREEN_PROJECT_SESSION);

        keys.add(REDIS_KEY_SET_SCREEN_SESSION_COMPUTER);
        keys.add(REDIS_KEY_SET_SCREEN_COMPUTER_SESSION);

        keys.add(REDIS_KEY_SET_SCREEN_SESSION_DUID);
        keys.add(REDIS_KEY_SET_SCREEN_DUID_SESSION);
        keys.add(REDIS_KEY_WS_SCREEN_INSTANCE);
        keys.add(sessionKey.split("@")[0]);
        return (String)redisTemplate.execute(unBindScreenScript, keys);
    }

    public String bindScreenSocketProject(String sessionKey, String projectId) {
        // 当前session是否存在绑定的项目，如果存在则行清除绑定关系
        // session与项目绑定

        if(StringUtils.isEmpty(sessionKey) || StringUtils.isEmpty(projectId)) throw new CustomException(ResultCode.PARAM_NOT_COMPLETE);
        List<String> keys = new ArrayList<>();
        keys.add(REDIS_KEY_HASH_SCREEN_SESSION_PROJECT);
        keys.add(REDIS_KEY_SET_SCREEN_PROJECT_SESSION);
        keys.add(sessionKey);
        keys.add(projectId);
        redisTemplate.execute(bindScreenProjectScript, keys);
        return null;
    }
}
